﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using SimpleIR;

namespace PInfoModel.Analyses
{
	public static class TypeAndMethodConstnessExtractor
	{
		public static TypeAndMethodConstnessInfo ExtractGeneralInfo(CompleteAnalysisInfoForProgram pip)
		{
			TypeAndMethodConstnessInfo cinfo = new TypeAndMethodConstnessInfo(pip.Program);

			foreach(CompleteAnalysisInfoMethod minfo in pip.MethodInfo.Values)
			{
				if(minfo.InvokeID.DefInClass.TypeDescription.IsBuiltinType)
					continue;

				cinfo.ProcessSingleMethod(minfo, pip.Program.MethodBodyTable.GetBodyForInvokeExactSigMatch(minfo.InvokeID));
			}

			return cinfo;
		}
	}

	/// <summary>
	/// A class that accumulates and manages general info about the constness of methods and types.
	/// </summary>
	public sealed class TypeAndMethodConstnessInfo
	{
		/// <summary>
		/// Classes which are instantiated by the program.
		/// </summary>
		public readonly HashSet<IRType> AllocatedClasses;

		/// <summary>
		/// Classes which are constant in the program.
		/// </summary>
		public readonly HashSet<IRType> ConstClasses;

		/// <summary>
		/// Info on the constness of each method which is invoked by the program.
		/// </summary>
		public readonly Dictionary<IRInvokeIdentity, MethodConstnessInfo> MethodConsts;
		
		public TypeAndMethodConstnessInfo(IRProgram p)
		{
			this.AllocatedClasses = new HashSet<IRType>();
			this.ConstClasses = new HashSet<IRType>();

			List<IRType> allt = p.TypeTable.AllDefinedTypes;
			foreach(IRType tt in allt)
			{
				if(tt.TypeDescription.IsBuiltinType || tt.TypeDescription.IsAbstractType || (tt.TypeDescription.TypeQualifierInfo & TypeQualifiers.InterfaceTag) != TypeQualifiers.Clear)
					continue;

				if(tt is IRClassType && (tt.TypeDescription.TypeQualifierInfo & TypeQualifiers.StaticTag) == TypeQualifiers.Clear)
					this.ConstClasses.Add(tt);
			}

			this.MethodConsts = new Dictionary<IRInvokeIdentity, MethodConstnessInfo>();
		}

		public void ProcessSingleMethod(CompleteAnalysisInfoMethod minfo, IRBodyDef bdf)
		{
			MethodConstnessInfo mci = MethodConstnessInfo.ProcessSingleMethod(minfo.InvokeID, minfo.SummaryInState, minfo.SummaryOutState);
			this.MethodConsts.Add(minfo.InvokeID, mci);

			foreach(CompleteAnalysisInfoMethodContext ccm in minfo.AllCallContexts)
				this.EliminateNonConstTypes(minfo.InvokeID, bdf.Body.BasicBlockList, ccm);
		}

		private void EliminateNonConstTypes(IRInvokeIdentity iiv, List<IRBasicBlock> bbset, CompleteAnalysisInfoMethodContext ccm)
		{
			if(iiv.Sig.InvokeName.Equals(".cctor"))
				return;

			bool skipconsrcvr = iiv.Sig.InvokeName.Equals(".ctor");
			Dictionary<int, ProgramPointInfo> ppinfo = ccm.ProgramPointToPointInfoMap();

			foreach(IRBasicBlock bb in bbset)
			{
				if(!ccm.BlockIDToStateMap.ContainsKey(bb.BlockID))
					continue;

				foreach(IRByteCode bc in bb.Operations)
				{
					ProgramPointInfo ppi;
					PIModel pm;
					PISNode evstck;
					switch(bc.OperationCode)
					{
						case OpCode.Op.AllocCons:
							IRType tt = ((IRAllocationOp)bc).AllocType;
							if(tt is IRClassType && !tt.TypeDescription.IsBuiltinType)
								this.AllocatedClasses.Add(tt);
							break;
						case OpCode.Op.IndirectInitialize:
							ppi = ppinfo[bc.UseModLoc];
							if(ppi != null)
							{
								pm = ppi.PreOperationModel;
								evstck = pm.GetEvalStackNodeAtPos(0);
								foreach(PIMEdge inittrgts in evstck.Targets)
									this.ConstClasses.Remove(pm.Nodes[inittrgts.TargetAddress].LocationType);
							}
							break;
						case OpCode.Op.IndirectWrite:
						case OpCode.Op.StoreMemberField:
							SymbolicEvalStackTerm evst = bc.SymEvalStack.GetTermAtPos(1);
							bool isthisrcvr = (evst.SymTerm == SymbolicEvalStackTerm.TermTag.Var || evst.SymTerm == SymbolicEvalStackTerm.TermTag.AddressOfVar) && evst.TermRoot.NameString.Equals("this");
							bool consskip = (skipconsrcvr & isthisrcvr);

							if(!consskip)
							{
								ppi = ppinfo[bc.UseModLoc];
								if(ppi != null)
								{
									pm = ppi.PreOperationModel;
									evstck = pm.GetEvalStackNodeAtPos(1);
									foreach(PIMEdge inittrgts in evstck.Targets)
										this.ConstClasses.Remove(pm.Nodes[inittrgts.TargetAddress].LocationType);
								}
							}
							break;
						default:
							break;
					}
				}
			}
		}

		public string EmitString()
		{
			StringBuilder sb = new StringBuilder(AnalyisSupport.InfoHeader + "Constness Analysis Metrics\n");

			sb.Append(AnalyisSupport.InfoTitle + "Constant Types:\n");
			
			List<IRType> ctl = new List<IRType>(this.ConstClasses);
			ctl.Sort((IRType t1, IRType t2) => t1.ToString().CompareTo(t2.ToString()));
			foreach(IRType tt in ctl)
				sb.Append(tt.ToString() + "\n");
			sb.Append("\n");

			sb.Append(AnalyisSupport.InfoTitle + "Method Constness:\n");
			List<IRInvokeIdentity> iivl = new List<IRInvokeIdentity>(this.MethodConsts.Keys);
			iivl.Sort((IRInvokeIdentity i1, IRInvokeIdentity i2) => (i1.DefInClass.ToString().CompareTo(i2.DefInClass.ToString()) != 0) ? i1.DefInClass.ToString().CompareTo(i2.DefInClass.ToString()) : i1.ToString().CompareTo(i2.ToString()));
			for(int i = 0; i < iivl.Count; ++i)
			{
				IRInvokeIdentity ii = iivl[i];

				if(i == 0 || iivl[i - 1].DefInClass != iivl[i].DefInClass)
				{
					if(i != 0)
						sb.Append("\n");

					sb.Append(ii.DefInClass.ToString() + "\n");
				}
				
				sb.Append("\t" + this.MethodConsts[ii].EmitString() + "\n");
			}
			sb.Append("\n");
		
			return sb.ToString();
		}
	}

	/// <summary>
	/// A class that accumulates and manages general info about the constness of methods.
	/// </summary>
	public sealed class MethodConstnessInfo
	{
		/// <summary>
		/// The method that this constntess information is for.
		/// </summary>
		public readonly IRInvokeIdentity Invoke;

		/// <summary>
		/// True if the method is constant in all externally visible state.
		/// </summary>
		public readonly bool IsMethodConst;

		/// <summary>
		/// True if the method is constant in all externally visible state except for the RECIEVER parameter.
		/// </summary>
		public readonly bool IsMethodRcvrConst;

		/// <summary>
		/// A map from parameter names to boolean (true) representing if the parameter is constant in the method.
		/// </summary>
		public readonly Dictionary<string, bool> ParameterConstness;

		private MethodConstnessInfo(IRInvokeIdentity ii, bool ic, bool irc, Dictionary<string, bool> pc)
		{
			this.Invoke = ii;
			this.IsMethodConst = ic;
			this.IsMethodRcvrConst = irc;
			this.ParameterConstness = pc;
		}

		internal static MethodConstnessInfo ProcessSingleMethod(IRInvokeIdentity iiv, PIModel mentry, PIModel mexit)
		{
			bool isconst = mexit == null || mexit.Nodes.All((PIMNode n) => !n.UseModInfo.Any((PIUseMod um) => um.Tag == PIUseMod.Value.ModAsExternal));

			bool rcvrconst = iiv.Sig.IsVirtualInvoke;
			
			Dictionary<string, bool> pconst = new Dictionary<string, bool>();
			foreach(IRRootIdentifier rr in mentry.NameToNodeMap.Keys)
			{
				HashSet<MemLocID> locs = new HashSet<MemLocID>();
				mentry.GetIDSOfAllReachableNodesFromRoot(rr, locs);
				HashSet<PIMNode> nexit = (mexit == null) ? new HashSet<PIMNode>() : mexit.GetAllNodesAssociatedWithIDSet(locs);

				bool isrootconst = nexit.All((PIMNode n) => !n.UseModInfo.Any((PIUseMod um) => um.Tag == PIUseMod.Value.ModAsExternal));
				if(!isrootconst)
					rcvrconst &= (rr is IRVarName && ((IRVarName)rr).NameString.Equals("this"));

				if(rr is IRVarName)
					pconst.Add(((IRVarName)rr).NameString, isrootconst);
			}

			return new MethodConstnessInfo(iiv, isconst, rcvrconst, pconst);
		}

		public string EmitString()
		{
			StringBuilder sb = new StringBuilder();

			if(this.IsMethodConst)
				sb.Append("const ");
			else if(this.IsMethodRcvrConst)
				sb.Append("exrcvrconst ");
			else
			{ ;}

			sb.Append(this.Invoke.Sig.InvokeName + " -> {");
			List<string> pmcs = new List<string>(this.ParameterConstness.Keys);
			pmcs.Sort();

			for(int i = 0; i < pmcs.Count; ++i)
			{
				if(i != 0)
					sb.Append(", ");

				sb.Append((this.ParameterConstness[pmcs[i]] ? "const " : "") + pmcs[i]);
			}
			sb.Append("}");

			return sb.ToString();
		}
	}
}
